package magnet.processor.instances.generator;

import com.squareup.javapoet.*;
import magnet.internal.Generated;
import magnet.internal.InstanceFactory;
import magnet.processor.instances.*;
import magnet.processor.instances.aspects.disposer.DisposeMethodGenerator;
import magnet.processor.instances.aspects.disposer.IsDisposableMethodGenerator;
import magnet.processor.instances.aspects.factory.DefaultCreateMethodGenerator;
import magnet.processor.instances.aspects.limitedto.GetLimitMethodGenerator;
import magnet.processor.instances.aspects.scoping.GetScopingMethodGenerator;
import magnet.processor.instances.aspects.selector.GetSelectorMethodGenerator;
import magnet.processor.instances.aspects.siblings.GetSiblingTypesMethodGenerator;

import javax.lang.model.element.Modifier;

public class FactoryTypeCodeGenerator implements FactoryTypeVisitor, CodeGenerator{

    private TypeSpec factoryTypeSpec = null;
    private ClassName factoryClassName = null;
    private boolean generateGettersInCreateMethod = false;

    private final Aspect aspectGetSiblingTypes = new Aspect(new GetSiblingTypesMethodGenerator());
    private final Aspect aspectGetScoping = new Aspect(new GetScopingMethodGenerator());
    private final Aspect aspectGetLimit = new Aspect(new GetLimitMethodGenerator());
    private final Aspect aspectGetSelector = new Aspect(new GetSelectorMethodGenerator());
    private final DefaultCreateMethodGenerator createMethodGenerator = new DefaultCreateMethodGenerator();
    private final IsDisposableMethodGenerator isDisposableMethodGenerator = new IsDisposableMethodGenerator();
    private final DisposeMethodGenerator disposeMethodGenerator = new DisposeMethodGenerator();

    @Override
    public CodeWriter generateFrom(FactoryType factoryType) throws IllegalAccessException {
        factoryType.accept(this);
        return new CodeWriter(this.factoryClassName.packageName(),factoryTypeSpec);
    }

    @Override
    public void enterFactoryClass(FactoryType factoryType) throws IllegalAccessException {
        generateGettersInCreateMethod = factoryType.getCustomFactoryType() == null;
        createMethodGenerator.visitFactoryClass(factoryType);
        isDisposableMethodGenerator.visitFactoryClass(factoryType);
        disposeMethodGenerator.visitFactoryClass(factoryType);
    }

    @Override
    public void enterCreateMethod(CreateMethod createMethod) {
        factoryTypeSpec = null;
        createMethodGenerator.enterCreateMethod(createMethod);
    }

    @Override
    public void visitCreateMethodParameter(MethodParameter parameter) {
        createMethodGenerator.visitCreateMethodParameter(parameter);
    }

    @Override
    public void exitCreateMethod(CreateMethod createMethod) {
        createMethodGenerator.exitCreateMethod();
    }

    @Override
    public void visit(GetScopingMethod method) {
        aspectGetScoping.visit(new Aspect.VisitBlock<GetScopingMethodGenerator>() {

            @Override
            public void block(GetScopingMethodGenerator generator) {
                generator.visit(method);
            }
        });
    }

    @Override
    public void visit(GetLimitMethod method) {
        aspectGetLimit.visit(new Aspect.VisitBlock<GetLimitMethodGenerator>() {

            @Override
            public void block(GetLimitMethodGenerator generator) {
                generator.visit(method);
            }
        });
    }

    @Override
    public void enterSiblingTypesMethod(GetSiblingTypesMethod method) {
        aspectGetSiblingTypes.visit(new Aspect.VisitBlock() {
            @Override
            public void block(AspectGenerator generator) {
                ((GetSiblingTypesMethodGenerator)generator).enterSiblingTypesMethod(method);
            }
        });
    }

    @Override
    public void visitSiblingType(ClassName type) {
        aspectGetSiblingTypes.visit(new Aspect.VisitBlock() {
            @Override
            public void block(AspectGenerator generator) {
                ((GetSiblingTypesMethodGenerator)generator).visitSiblingType(type);
            }
        });
    }

    @Override
    public void exitSiblingTypesMethod(GetSiblingTypesMethod method) {
        aspectGetSiblingTypes.visit(new Aspect.VisitBlock<GetSiblingTypesMethodGenerator>() {

            @Override
            public void block(GetSiblingTypesMethodGenerator generator) {
                generator.exitSiblingTypesMethod();
            }
        });
    }

    @Override
    public void enterGetSelectorMethod(GetSelectorMethod method) {
        aspectGetSelector.visit(new Aspect.VisitBlock() {
            @Override
            public void block(AspectGenerator generator) {
                ((GetSelectorMethodGenerator)generator).enterGetSelectorMethod(method);
            }
        });
    }

    @Override
    public void visitSelectorArgument(String argument) {
        aspectGetSelector.visit(new Aspect.VisitBlock() {
            @Override
            public void block(AspectGenerator generator) {
                ((GetSelectorMethodGenerator)generator).visitSelectorArgument(argument);
            }
        });
    }

    @Override
    public void exitGetSelectorMethod(GetSelectorMethod method) {
        aspectGetSelector.visit(new Aspect.VisitBlock<GetSelectorMethodGenerator>() {

            @Override
            public void block(GetSelectorMethodGenerator generator) {
                generator.exitGetSelectorMethod();
            }
        });
    }

    @Override
    public void exitFactoryClass(FactoryType factory) {
        factoryClassName = factory.getFactoryType();

        TypeSpec.Builder classBuilder = TypeSpec
                .classBuilder(factoryClassName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addAnnotation(Generated.class)
                .superclass(generateFactorySuperInterface(factory));

        createMethodGenerator.generate(classBuilder);

        aspectGetScoping.generate(classBuilder);
        aspectGetLimit.generate(classBuilder);
        aspectGetSiblingTypes.generate(classBuilder);
        aspectGetSelector.generate(classBuilder);
        isDisposableMethodGenerator.generate(classBuilder);
        disposeMethodGenerator.generate(classBuilder);

        classBuilder
                .addMethod(generateGetTypeMethod(factory));

        factoryTypeSpec = classBuilder.build();
    }

    private MethodSpec generateGetTypeMethod(FactoryType factoryType) {
        return MethodSpec
                .methodBuilder("getType")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(Class.class)
                .addStatement("return $T.class", factoryType.getInterfaceType())
                .build();
    }

    private TypeName generateFactorySuperInterface(FactoryType factoryType) {
        return ParameterizedTypeName.get(
                ClassName.get(InstanceFactory.class),
                factoryType.getInterfaceType()
        );
    }
}
